package org.ns.socket.client.handler;


import org.ns.socket.client.SocketContext;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class NioSocketHandler extends AbstractSocketHandler {

    private static Selector selector;
    private static Thread selectorThread;
    private static Map<SocketChannel, NioSocketHandler> channelMap = new ConcurrentHashMap();
    static {
        try {
            selector = Selector.open();
            Thread selectorThread = new Thread(NioSocketHandler::selectorRun, "NIO-SOCKET-SELECTOR");
            selectorThread.setDaemon(true);
            selectorThread.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static void selectorRun() {
        while (true) {
            try {
                selector.select(3000);
                Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey selectionKey = iterator.next();
                    iterator.remove();

                    if (!selectionKey.isValid()) {
                        continue;
                    }

                    if (selectionKey.isReadable()) {
                        SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
                        NioSocketHandler nioSocketHandler = channelMap.get(socketChannel);
                        if (nioSocketHandler == null) {
                            selectionKey.cancel();
                            socketChannel.close();
                            continue;
                        }
                        nioSocketHandler.run();
                    }
                }

            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }



    private SocketChannel socketChannel;
    private SelectionKey selectionKey;
    private org.ns.socket.client.ByteBuffer readCahce = new org.ns.socket.client.ByteBuffer(10240);
    private SocketContext socketContext = new SocketContext(this);

    @Override
    public void connect(String host, int port) throws IOException {
        socketChannel = SocketChannel.open();
        channelMap.put(socketChannel, this);
        socketChannel.connect(new InetSocketAddress(host, port));
        socketChannel.configureBlocking(false);
        selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);

    }

    @Override
    public void write(Object msg) {
        synchronized (socketChannel) {
            try {
                if (socketChannel.isOpen() && socketChannel.isConnected()) {
                    byte[] buf = writeHandler(msg);
                    socketChannel.write(ByteBuffer.wrap(buf));
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void close() throws IOException {
        selectionKey.cancel();
        socketChannel.close();
        channelMap.remove(socketChannel);
    }


    public void run() {

        try {
            ByteBuffer byteBuffer = ByteBuffer.allocate(10240);
            int length = socketChannel.read(byteBuffer);
            byteBuffer.flip();
            byte[] buf = new byte[length];
            byteBuffer.get(buf);
            readCahce.write(buf);

            readHandler(readCahce, socketContext);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
